package com.basker.pisces.domain.entity.data;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.PageRowList;
import com.basker.pisces.core.RowContract;
import com.basker.pisces.core.meta.bean.BeanFieldDescriptor;
import com.basker.pisces.core.query.IQuery.PagingInfo;
import com.basker.pisces.core.query.QueryInfo;
import com.basker.pisces.core.query.builder.CriterionBuilder;
import com.basker.pisces.core.query.criteria.CompositeCriterion;
import com.basker.pisces.core.query.criteria.Criterion;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.repository.IDataRepository;
import com.basker.pisces.domain.annotation.PropertyOutput;
import com.basker.pisces.domain.annotation.PropertyOutput.ValueType;
import com.basker.pisces.domain.constant.DomainConstants;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.property.meta.StatusProperty;
import com.basker.pisces.domain.value.IValueObject;
import com.basker.pisces.utils.ReflectionUtils;

public abstract class AbstractRowListDataEntity implements IRowListDataEntity {
    @Autowired(required = false)
    private IDataRepository repository;

    private Entity entity;
    private CompositeCriterion criteria = new CompositeCriterion(
            AbstractRowListDataEntity.class.getSimpleName() + "'s criteria");

    public AbstractRowListDataEntity(Entity entity, IDataRepository repository) {
        this.setEntity(entity);
        this.repository = repository;

        this.tryExcludeDeletedRows();
    }

    @Override
    public void addCriterion(Criterion criterion) {
        criteria.addCriterion(criterion);
    }

    @Override
    public Entity getEntity() {
        return entity;
    }

    private void setEntity(Entity entity) {
        this.entity = entity;
    }

    @Override
    public <T> List<T> output(List<RowContract> rowList, Class<T> outputType) {
        Assert.notNull(outputType, "parameter 'outputType' is required");

        if (CollectionUtils.isEmpty(rowList)) {
            return Collections.emptyList();
        }

        List<T> outputList = rowList.stream().map(r -> ReflectionUtils.createNewInstance(outputType))
                .collect(Collectors.toList());

        BeanFieldDescriptor[] beanFieldDescriptors = BeanFieldDescriptor.getBeanFieldDescriptors(outputType);
        for (BeanFieldDescriptor beanFieldDescriptor : beanFieldDescriptors) {
            for (int index = 0; index < rowList.size(); index++) {
                RowContract row = rowList.get(index);
                Object output = outputList.get(index);

                this.fillOutputValue(beanFieldDescriptor, row, output);
            }
        }

        return outputList;
    }

    @Override
    public PageRowList query(QueryInfo queryInfo, PagingInfo pagingInfo) {
        QueryMeta queryMeta = this.getEntity().getQueryMeta();

        if (!this.criteria.isEmpty()) {
            queryInfo.addCriterion(this.criteria);
        }

        if (pagingInfo != null && pagingInfo.isPagingQuery()) {
            return repository.queryForPageRows(queryMeta, queryInfo, pagingInfo);
        } else {
            List<RowContract> rows = this.repository.queryForRows(queryMeta, queryInfo);
            return new PageRowList(rows, rows.size(), PagingInfo.FIRST_PAGE_INDEX, rows.size());
        }
    }

    private void fillOutputValue(BeanFieldDescriptor beanFieldDescriptor, RowContract row, Object output) {
        PropertyOutput propertyOutput = beanFieldDescriptor.getAnnotation(PropertyOutput.class);
        if (propertyOutput == null) {
            return;
        }

        if (beanFieldDescriptor.isCollectionType()) {
            // PropertyOutput标记列表属性，忽略，只有IDataEntity.output才支持子实体的集合输出
            return;
        }

        String propertyAlias = this.getPropertyAlias(beanFieldDescriptor, propertyOutput);
        if (!row.containsKey(propertyAlias)) {
            throw new IllegalArgumentException("PropertyOutput:'" + propertyAlias + "' not found!");
        }

        Object rawValue = row.get(propertyAlias);
        Object value = rawValue;

        if (propertyOutput.valueType().equals(ValueType.PresentValue)) {
            Property<?> property = this.getEntity().getPropertyByAlias(propertyAlias);
            IValueObject valueObject = property.createValue(rawValue);

            value = valueObject.getPresentValue();
        }

        if (value != null) {
            beanFieldDescriptor.setValue(output, value, true);
        }
    }

    private String getPropertyAlias(BeanFieldDescriptor beanFieldDescriptor, PropertyOutput propertyOutput) {
        String propertyAlias = propertyOutput.value();
        if (StringUtils.isEmpty(propertyAlias)) {
            propertyAlias = propertyOutput.propertyAlias();
        }

        if (StringUtils.isEmpty(propertyAlias)) {
            propertyAlias = beanFieldDescriptor.getName();
        }

        return propertyAlias;
    }

    private void tryExcludeDeletedRows() {
        StatusProperty[] statusProperties = this.entity.getStatusProperties();
        if (ArrayUtils.isEmpty(statusProperties)) {
            return;
        }

        Optional<StatusProperty> statusProperty =
                Stream.of(statusProperties).filter(p -> p.containsStatusValue(DomainConstants.DataStatus.DELETE)).findFirst();
        statusProperty.ifPresent(sp ->
                this.addCriterion(
                        CriterionBuilder.notEqual(sp.getAlias(), DomainConstants.DataStatus.DELETE)
                                .createCriterion(this.entity.getQueryMeta())
                )
        );
    }
}
